package com.bite.messageQueue.mqServer.datacenter;

import com.bite.messageQueue.common.BinaryTool;
import com.bite.messageQueue.common.MqException;
import com.bite.messageQueue.mqServer.core.MSGQueue;
import com.bite.messageQueue.mqServer.core.Message;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.util.LinkedList;
import java.util.Scanner;

/**
 * 通过这个类, 来针对硬盘上的消息进行管理
 */
@Slf4j
public class MessageFileManager {
    /**
     * 内部类,用来表示队列的消息总数和有效消息数
     */
    static public class Stat {
        public int totalCount;
        public int validCount;
    }

    public void init(){}

    /**
     * 用来获取指定队列的消息文件所在路径
     *
     * @param queueName 队列名称
     * @return 文件所在路径
     */
    private String getQueueDir(String queueName) {
        return "./data/" + queueName;
    }


    /**
     * 获取指定队列的消息的数据文件所在路径
     * @param queueName 队列名称
     * @return 文件所在路径
     */
    private String getQueueDataPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data.txt";
    }

    /**
     * 获取指定队列的消息的数据统计文件所在路径
     * @param queueName 队列名称
     * @return 文件所在路径
     */
    private String getQueueStatPath(String queueName) {
        return getQueueDir(queueName) + "/queue_stat.txt";
    }

    /**
     * 创建队列对应的数据文件
     * @param queueName 队列名称
     */
    public void createQueue(String queueName) throws IOException {
        File dir = new File(getQueueDir(queueName));
        if (!dir.exists()) {
            boolean ok = dir.mkdirs();
            if (!ok) {
                log.error("{}该队列的数据库目录未创建！", getQueueDir(queueName));
            }
        }

        File dataFile = new File(getQueueDataPath(queueName));
        if (!dataFile.exists()) {
            boolean newDataFile = dataFile.createNewFile();
            if (!newDataFile) {
                log.error("{}该队列的数据文件未创建！", getQueueDataPath(queueName));
            }
        }

        File statFile = new File(getQueueStatPath(queueName));
        if (!statFile.exists()) {
            boolean newStatFile = statFile.createNewFile();
            if (!newStatFile) {
                log.error("{}该队列的统计文件未创建！", getQueueStatPath(queueName));
                throw new RuntimeException();
            }
        }
        Stat stat = new Stat();
        stat.validCount = 0;
        stat.totalCount = 0;
        writeStat(queueName, stat);
    }

    /**
     * 删除队列对应的数据库文件
     * @param queueName 队列名称
     */
    public void destroyQueue(String queueName) {
        File queueDataFile = new File(getQueueDataPath(queueName));
        boolean data = queueDataFile.delete();
        if (!data) {
            log.error("{}该队列的数据文件删除失败！", getQueueDataPath(queueName));
        }
        File queueStatFile = new File(getQueueStatPath(queueName));
        boolean stat = queueStatFile.delete();
        if (!stat) {
            log.error("{}该队列的统计文件删除失败！", getQueueStatPath(queueName));
        }
        File dir = new File(getQueueDir(queueName));
        boolean dirOk = dir.delete();
        if (!dirOk) {
            log.error("{}该队列的数据库目录删除失败！", getQueueDir(queueName));
        }
    }

    /**
     * 检查队列的目录和文件是否存在
     * @param queueName 队列名称
     * @return true表示存在，false表示不存在
     */
    public boolean checkFilesExits(String queueName) {
        File queueDataFile = new File(getQueueDataPath(queueName));
        if (!queueDataFile.exists()) {
            return false;
        }
        File queueStatFile = new File(getQueueStatPath(queueName));
        if (!queueStatFile.exists()) {
            return false;
        }
        return true;
    }

    /**
     * 读取队列的统计信息
     * @param queueName 队列名称
     * @return 队列的统计信息
     */
    private Stat readStat(String queueName) {
        Stat stat = new Stat();
        try (InputStream inputStream = new FileInputStream(getQueueStatPath(queueName))) {
            Scanner scanner = new Scanner(inputStream);
            stat.totalCount = scanner.nextInt();
            stat.validCount = scanner.nextInt();
            return stat;
        } catch (IOException e) {
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 更新队列的统计信息
     * @param queueName 队列名称
     * @param stat      队列的统计信息
     */
    private void writeStat(String queueName, Stat stat) {
        try (OutputStream outputStream = new FileOutputStream(getQueueStatPath(queueName))) {
            // 创建PrintWriter对象，用于向文件中写入数据
            PrintWriter pw = new PrintWriter(outputStream);
            // 将统计信息写入文件
            pw.write(stat.totalCount + "\t" + stat.validCount);
            // 刷新缓冲区，确保数据写入文件
            pw.flush();
        } catch (IOException e) {
            // 抛出运行时异常
            throw new RuntimeException(e);
        }
    }

    /**
     * 发送消息到队列(添加数据)
     * @param queue   队列
     * @param message 消息
     */
    public void sendMessage(MSGQueue queue, Message message) throws MqException, IOException {
        if (!checkFilesExits(queue.getName())) {
            throw new MqException("[MessageFileManager] 队列对应的文件不存在! queueName=" + queue.getName());
        }
        //序列化消息
        byte[] bytes = BinaryTool.toBytes(message);
        synchronized (queue) {
            //获取队列数据文件
            File queueDataFile = new File(getQueueDataPath(queue.getName()));
            //计算offsetBin和offsetEnd
            message.setOffsetBegin(queueDataFile.length() + 4);
            message.setOffsetEnd(message.getOffsetBegin() + 4 + bytes.length);
            //将消息数据写入数据文件
            try (OutputStream outputStream = new FileOutputStream(queueDataFile, true)) {
                try (DataOutputStream dos = new DataOutputStream(outputStream)) {
                    //写入消息长度
                    dos.writeInt(bytes.length);
                    //写入消息数据
                    dos.write(bytes);
                }
            }
            //更新队列的统计信息
            Stat stat = readStat(queue.getName());
            stat.totalCount++;
            stat.validCount++;
            writeStat(queue.getName(), stat);
        }
    }

    /**
     * 从数据库中删除消息(逻辑删除)
     * 1、先将Message数据读取出来，还原回Message对象，将其发送给目标服务器(消费者)
     * 2、随后删除该消息(将isValid改为0)
     * 3、最后再写回文件去
     * @param queue   队列
     * @param message 消息
     */
    public void deleteMessage(MSGQueue queue, Message message) throws MqException, IOException, ClassNotFoundException {
        synchronized (queue) {
            try (RandomAccessFile raf = new RandomAccessFile(getQueueDataPath(queue.getName()), "rw")) {
                byte[] bufferSrc = new byte[(int) (message.getOffsetEnd() - message.getOffsetBegin())];
                raf.seek(message.getOffsetBegin());
                raf.read(bufferSrc);
                //将字节数组反序列化为Message对象
                Message messageSrc = (Message) BinaryTool.fromBytes(bufferSrc);
                //删除
                messageSrc.setIsValid((byte) 0x0);
                //将消息写回文件
                raf.seek(message.getOffsetBegin());
                raf.write(BinaryTool.toBytes(messageSrc));
            }
        }
        //更新队列的统计信息
        Stat stat = readStat(queue.getName());
        if (stat.validCount > 0) {
            stat.validCount--;
        }
        writeStat(queue.getName(), stat);
    }

    /**
     * 从文件中读取所有有效数据数据
     * @param queueName 队列名称
     * @return 所有有效消息的列表
     */
    public LinkedList<Message> loadAllMessageFromQueue(String queueName) throws IOException, ClassNotFoundException {
        LinkedList<Message> messages = new LinkedList<>();
        try (InputStream inputStream = new FileInputStream(getQueueDataPath(queueName))) {
            try (DataInputStream dis = new DataInputStream(inputStream)) {
                //记录文件光标
                long offset = 0;
                while (true) {
                    //读取消息长度
                    int length = dis.readInt();
                    byte[] bytes = new byte[length];
                    //读取消息数据
                    int read = dis.read(bytes);
                    //判断读取长度是否一致
                    if (read != length) {
                        log.error("读取信息数据有误！");
                    }
                    //反序列化
                    Message message = (Message) BinaryTool.fromBytes(bytes);
                    //判断消息是否有效
                    if (message.getIsValid() == 0x1) {
                        message.setOffsetBegin(offset + 4);
                        message.setOffsetEnd(offset + 4 + length);
                        //更新文件光标
                        offset += 4 + length;
                        messages.add(message);
                    }
                }
            } catch (EOFException e) {
                //读取到文件末尾，结束循环
                log.info("信息的数据文件读取结束！");
            }
        }
        return messages;
    }

    /**
     * 检查信息状态文件内数据是否达到GC条件
     * @param queueName 队列名称
     * @return true表示需要GC，false表示不需要GC
     */
    public boolean checkGC(String queueName){
        Stat stat = readStat(queueName);
        if(stat.totalCount > 2000 && (double)stat.validCount / (double)stat.totalCount > 0.5){
            return true;
        }
        return false;
    }

    /**
     * 获取一个新的队列数据文件路径
     * 将有效数据复制过后，再将名字改回/queue_data.txt后缀
     * @param queueName 队列名称
     * @return 新的队列状态文件路径
     */
    private String getQueueDataNewPath(String queueName) {
        return getQueueDir(queueName) + "/queue_data_new.txt";
    }

    /**
     * 垃圾回收
     * @param queue 需要进行GC的队列
     * @throws IOException IO异常
     * @throws ClassNotFoundException 序列化异常
     */
    public void GC(MSGQueue queue) throws IOException, ClassNotFoundException, MqException {
        synchronized(queue){
            //获取GC的开始时间
            long startTime = System.currentTimeMillis();

            //创建新的队列数据文件对象
            File newDataFile = new File(getQueueDataNewPath(queue.getName()));
            if(newDataFile.exists()){
                throw new MqException("新的队列数据文件已存在！");
            }
            //创建新的队列数据文件
            boolean ok = newDataFile.createNewFile();
            if(!ok){
                throw new MqException("创建新的"+ queue.getName() +"队列数据文件失败！");
            }
            //读取所有有效数据信息
            LinkedList<Message> messages = loadAllMessageFromQueue(queue.getName());

            try(OutputStream outputStream = new FileOutputStream(newDataFile)){
                try(DataOutputStream dos = new DataOutputStream(outputStream)){
                    //遍历所有有效数据信息，将数据写入新的队列数据文件
                    for(Message message : messages){
                        //写入消息
                        byte[] bytes = BinaryTool.toBytes(message);
                        dos.writeInt(bytes.length);
                        dos.write(bytes);
                    }
                }
            }
            //删除旧的队列数据文件
            File oldDataFile = new File(getQueueDataPath(queue.getName()));
            boolean delete = oldDataFile.delete();
            if(!delete){
                throw new MqException("删除旧的"+ queue.getName() +"队列数据文件失败！");
            }
            //将新的队列数据文件改名为/queue_data.txt
            boolean rename = newDataFile.renameTo(oldDataFile);
            if(!rename){
                throw new MqException("将新的"+ queue.getName() +"队列数据文件重命名为/queue_data.txt失败！");
            }
            //更新队列状态文件
            Stat stat = readStat(queue.getName());
            stat.totalCount = messages.size();
            stat.validCount = messages.size();
            writeStat(queue.getName(), stat);
            //获取GC的结束时间
            long endTime = System.currentTimeMillis();
            log.info("GC完成，队列名称为：" + queue.getName() + "共耗时" + (endTime - startTime) + "ms");
        }
    }
}
